
//
// 101203 - AH - (Fantoft, Block E) Created.
// 101205 - AH - Renamed from ForestMatch.
// 101206 - AH - (Bien) methods for retrieval strategy algorithms.
// 101208 - AH - Moved from org.soekrat.engine.wheel
// 101212 - AH - (Bien) made TODO about probably getDeepest(List<Tree>) is not
//               needed and should be removed.
// 110114 - AH - Minor java doc fixes.
// 110519 - AH - (Garage) Logic that first try match complete sentence in place.
// 111002 - AH - Fixed error in java doc for a method.
//

package org.soekrat.engine.retrieval.forest;

import java.io.CharArrayWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import org.soekrat.engine.forest.Forest;
import org.soekrat.engine.forest.Tree;
import org.soekrat.engine.visitor.Input;

/**
 * <p>
 *   Information on how keywords matched in a
 *   {@link Forest}.
 * </p>
 * <p>
 *   Implements the methods specified in pseudo language for forest retrieval 
 *   strategies by Laetitia. 
 * </p>
 * @author Laetitia Le Chatton (LLC)
 * @author Arne Halvorsen (AH)
 */
public final class ForestScan 
{
  private final List<ForestScanDetail> m_matched=
    new ArrayList<ForestScanDetail>();
  
  /**
   * <p>
   *   Constructor.
   * </p>
   * @param forest   {@link Forest}.
   * @param input Keywords to match.
   */
  public ForestScan(Forest forest, Input input)
  {
    if (forest==null)
    {
      throw new NullPointerException("forest");
    }
    if (input==null)
    {
      throw new NullPointerException("keywords");
    }
    
    for (String keyword : input) 
    {
      ForestScanDetail fm=new ForestScanDetail(keyword);
      fm.match(forest);
      if (fm.getNumberOfMatches()>0) m_matched.add(fm);
    }
  }
  
  /*
  private List<ForestScanDetail> scan(Forest forest, Input input)
  {
    List<ForestScanDetail> retVal=new ArrayList<ForestScanDetail>();
    
    for (String keyword : input) 
    {
      ForestScanDetail fm=new ForestScanDetail(keyword);
      fm.match(forest);
      if (fm.getNumberOfMatches()>0) retVal.add(fm);
    }
    
    return retVal;
  }
  */
  
  @Override
  public String toString() 
  {
    CharArrayWriter caw=new CharArrayWriter();
    PrintWriter pw=new PrintWriter(caw);
    
    for (ForestScanDetail curr : m_matched) 
    {
      pw.println(curr);
      pw.println("----");
    }
    
    pw.flush();
    return caw.toString();
  }
  
  // Pseudo code concept implementation starts here. 
  
  /**
   * <p>
   *   Tells if no keywords matched.
   * </p>
   * @return 
   */
  public boolean noMatch(){ return m_matched.isEmpty(); }
  
  /**
   * <p>
   *   Tells if multiple keywords has been matched.
   * </p>
   * @return {@code true} if single keyword been matched.
   */
  public boolean multiKeyWord(){ return m_matched.size()>1; }
  
  /**
   * <p>
   *   Tells if only roots been matched by keywords.
   * </p>
   * @return {@code true} if is the case and {@code false} if not the case.
   */
  public boolean onlyRoot()
  {
    for (ForestScanDetail detail : m_matched)
    {
      if (!detail.onlyRoot()) return false;
    }
    return true;
  }
  
  /**
   * <p>
   *   Gets the "Key" (all trees matched). 
   * </p>
   * @return Found.
   */
  public List<Tree> getKey()
  {
    List<Tree> retVal=new ArrayList<Tree>();
    
    for (ForestScanDetail detail : m_matched) detail.getKey(retVal);
    
    return retVal;
  }
  
  /**
   * <p>
   *   Check if keyword matches at least one child in forest (in addition to a 
   *   root eventually).
   * </p>
   * @return {@code true} if is the case, {@code false} if is not the case.
   */
  public boolean matchChild()
  {
    for (ForestScanDetail  curr : m_matched)
    {
      if (curr.matchChild()) return true;
    }
    
    return false;
  }
  
  /**
   * <p>
   *   Check if keyword matches one child in forest (in addition to a root
   *   possible).
   * </p>
   * @return {@code true} if is the case, {@code false} if is not the case.
   */
  public boolean oneChildMatch()
  {
    int count=0;
    for (ForestScanDetail detail : m_matched)
    {
      if (detail.oneChildMatch()) count++;
      if (count>1) return false;
    }
    return count==1;
  }
  
  /**
   * <p>
   *   Gets the deepest
   *   {@link Tree} in given list.
   * </p>
   * @param trees {@code Tree}s.
   * @return One with longest path or {@code null} if {@code trees} empty.
   */
  // TODO : Was misunderstanding, need to return list from a list, remove this
  // later probably.
  public static Tree getDeepest(List<Tree> trees)
  {
    if (trees==null)
    {
      throw new NullPointerException("trees");
    }
    
    Tree retVal=null;
    
    int deepest=-1;
    
    for (Tree tree : trees)
    {
      int depth=tree.depth();
      if (depth>deepest) 
      {
        deepest=depth;
        retVal=tree;
      }
    }
    
    return retVal;
  }
  
  /**
   * <p>
   *   Gets random 
   *   {@link Tree} for list of trees.
   * </p>
   * @param trees {@code Tree}s.
   * @return Random.
   */
  public static Tree getRandom(List<Tree> trees)
  {
    if (trees==null)
    {
      throw new NullPointerException("trees");
    }
    
    int n=trees.size();
    
    switch (n)
    {
      case 0 : return null;
      case 1 : return trees.get(0);
      default :
      {
        Random random=new Random();
        int idx=random.nextInt(n);
        return trees.get(idx);
      }
    }
    
  }
  
}
